feat(cli): ca2a start, run the reference transport from a config file - #52
feat(cli): ca2a start, run the reference transport from a config file#52Susanpdl wants to merge 3 commits into
Conversation
Wire parse_peer_request into handle_peer_request on message/send so a callee can enforce a PeerRequest on a live inbound call. Keep the claim boundary: no attestation handshake and no seal-to-verified-measurement binding; sealed open uses a configured software key only. Signed-off-by: Susan Poudel <susanpdl77@gmail.com>
Require an explicit host in listen_addr instead of defaulting to 0.0.0.0, which tripped the hardcoded-bind-all-interfaces scan. Signed-off-by: Susan Poudel <susanpdl77@gmail.com>
imran-siddique
left a comment
There was a problem hiding this comment.
Reviewed the diff and thread. Scope is tight and matches the body: live inbound A2A JSON-RPC wiring for the second Tier 2 checkbox, with ordinary A2A returning ca2a: null rather than inventing a trust state, and malformed metadata plus denials failing closed with a structured ca2a_code. X25519 key handling validates length and is software-configured only, with the docs correctly caveating that it is not measurement-bound. No secrets in the diff; test keys are generated at runtime. Tests cover config, policy loader, and server paths, and all real checks are green. LGTM. Leaving the merge to a maintainer.
|
@Susanpdl this is approved and ready except for a merge conflict with main. Could you merge main into feat/live-a2a-inbound-serving (or rebase) and resolve the conflict? Once it is clean it can go in. Thanks. |
|
Happy to resolve the conflict, but I want to check the shape with you first, because #53 landed after your approval here and it overlaps with this branch. The conflict itself is small: CHANGELOG.md, LIMITATIONS.md, ROADMAP.md, docs/spec/transport.md, and pyproject.toml. No code conflicts. The bigger question is duplication. #53 added What is still missing on main and is the part of this PR I think is worth keeping:
So my suggestion is to refit this branch instead of merging it as-is: keep Want me to go that way, or would you rather keep the two transports separate (JSON-RPC alongside the reference server), or just close this and open a smaller CLI-only PR? Happy to do whichever you prefer. |
…port Merge main and refit this branch onto the transport that landed in agentrust-io#53. The JSON-RPC listener here duplicated transport.server, so it is gone along with the starlette/uvicorn [serve] extra. ca2a start now resolves a policy and a provider from the config file, builds a PeerNode, and serves it with transport.server, which is standard library only. The wiring lives in ca2a_runtime.bootstrap so the CLI adds no capability the library did not already have. Provider selection fails closed. software-only carries no hardware guarantee, so auto refuses to start when no confidential-computing platform is detected rather than silently choosing it, and a named hardware provider whose device node is absent is a startup error. Drop enclave_private_key_hex: a PeerNode generates its own channel keypair and publishes it through the handshake, so a pre-shared key in a config file would be sealing to something nobody appraised. Default listen_addr to 127.0.0.1:8443, matching transport.server.serve, now that something actually binds it. Signed-off-by: Susan Poudel <susanpdl77@gmail.com>
|
@carloshvp refit onto #53, ready for another look. Short version: your reference transport made my Starlette listener redundant, so I deleted it rather than shipping two servers. The Three judgment calls I would like you to check:
Claim boundary is unchanged: software mode is 237 tests pass, ruff/mypy/bandit clean, and I smoke-tested it against a real socket. Unrelated, but worth knowing: running pytest rewrites |
Welcome to Codecov 🎉Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests. ℹ️ You can also turn on project coverage checks and project coverage reporting on Pull Request comment Thanks for integrating Codecov - We've got you covered ☂️ |
imran-siddique
left a comment
There was a problem hiding this comment.
Approving. All real checks pass on every platform and Python version; the only red is the maintainer-hold gate.
Reviewed the substance rather than the diffstat, and the two decisions that mattered are both right:
Provider selection fails closed. auto never degrades to software-only, and a named hardware provider whose device node is absent is a startup error rather than a silent downgrade. That is the same posture the rest of this stack holds to, and it is the difference between a software-mode listener being a stated configuration and being an accident.
Loopback default. Changing listen_addr from 0.0.0.0:8443 to 127.0.0.1:8443 was the important call, and you made it unprompted with the right reasoning: the old value was harmless while nothing read it, and the moment ca2a start binds it, defaulting to every interface would put a peer on the network by omission. Requiring the host explicitly is better than a permissive default with a warning.
Also good: load_policy refusing to start without one of policy_bundle_path or local_policy, since a node with no policy intersects every delegated scope to nothing and that reads as misconfiguration rather than a decision. Relative-path resolution against the config directory, the empty-file check, and OSError wrapped into ConfigError are all the right level of care for a config path.
Test coverage is proportionate (274 lines of tests for 216 of source), and the CHANGELOG is honest about what this does not do, including that attestation remains one-directional and software mode is still the default posture.
Leaving the merge to @imran-siddique.
Closes the
ca2a startgap the transport spec listed as "Not yet: serving is viatransport.server.serve".What changed since the first review
This branch originally shipped its own Starlette JSON-RPC listener. #53 landed a reference HTTP transport,
PeerNode, and the attestation handshake while this was open, which made that listener duplicate work and, worse, seal against a key nobody had appraised. So the branch is refit rather than rebased: the Starlette server, the[serve]extra, and the starlette/uvicorn/httpx dependencies are gone. What is left is the config-driven entry point.What it does
ca2a start --config ca2a-config.yamlresolves a policy and an attestation provider from the config file, builds aPeerNode, and serves it withca2a_runtime.transport.server. No new dependencies, since the reference transport is standard library only.The wiring lives in
ca2a_runtime.bootstrap(load_policy,select_provider,build_peer_node) so it is usable without the CLI, and so the CLI adds no capability the library did not already have. A program that already holds aPolicyand a provider should keep constructing aPeerNodedirectly.Decisions worth a look
Provider selection fails closed, following the rule in
tee/software.pythat a no-guarantee posture is never a silent fallback.autorefuses to start when no confidential-computing platform is detected instead of quietly pickingsoftware-only, and a named hardware provider whose device node is absent is a startup error rather than a failure at the first handshake. Software mode prints the assurance a caller will actually appraise, so an operator does not have to infer it.enclave_private_key_hexis dropped. APeerNodegenerates its own X25519 channel keypair and publishes the public half through the handshake, so a pre-shared key in a config file would mean sealing to something nobody appraised. That was the right model before #53 and the wrong one after it.listen_addrnow defaults to127.0.0.1:8443and must name a host explicitly. It was0.0.0.0:8443while nothing read it; now that something binds it, defaulting to every interface would put a peer on the network by omission.transport.server.servealready defaulted to loopback, so the two now agree. Bad addresses failca2a validate-configrather than at bind time, and bracketed IPv6 parses.Claim boundary
Unchanged from #53. Software mode is
assurance="none", and the seal is not bound to a hardware-verified measurement.ca2a startdoes not move that line, it just makes the existing software-mode path runnable from a config file. LIMITATIONS.md, ROADMAP.md, and the transport spec are updated to say exactly that.Tests
237 pass, 3 skipped. New coverage in
tests/unit/test_bootstrap.py(policy resolution, provider fail-closed behavior, and a config-built node serving a real HTTP call) andtests/unit/test_cli_start.py(whatca2a startbinds, and the four ways it refuses to start). ruff, mypy, and bandit are clean.Also smoke-tested against a real socket: the handshake endpoint returns the offer, an unknown path 404s, and a port conflict exits 1 with
cannot bind 127.0.0.1:8443instead of a traceback.One thing for you
Running the suite rewrites
examples/rejection-with-proof/chain.jsonanddag.jsonwith fresh keys, since the test executesdemo.pyand the demo generates keypairs. I reverted the churn here, but it will bite anyone who runs pytest before committing. Happy to open a separate issue.